/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.worldwizards.saddl;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Jeffrey Kesselman
 */
public class SADDL {
    static private Logger logger = Logger.getLogger(SADDL.class.getName());
    static private ThreadLocal<ParserVars> parserVars =
            new ThreadLocal<ParserVars>() {

                @Override
                protected ParserVars initialValue() {
                    return new ParserVars();
                }
            };

    private static class ParserVars {

        String classPrefix = "";
        int linecount = 0;
    }

    private static int getLineCount() {
        return parserVars.get().linecount;
    }

    private static void setLineCount(int lineCount) {
        parserVars.get().linecount = lineCount;
    }

    private static void setClassPrefix(String prefix) {
        if (!prefix.endsWith(".")) {
            prefix = prefix + ".";
        }
        parserVars.get().classPrefix = prefix;
    }

    private static String getClassPrefix() {
        return parserVars.get().classPrefix;
    }

    public static List parse(Reader rdr) throws IOException {
        BufferedReader brdr = new BufferedReader(rdr);
        List list = new ArrayList();
        recursiveParse(getLine(brdr), brdr, list);
        return list;
    }
//

    private static int countIndent(String line) {
        if (line == null) {
            return 0;
        }
        int i = 0;
        while ((i < line.length()) && (Character.isWhitespace(line.charAt(i)))) {
            i++;
        }
        return i;
    }

    private static LineRecord getLine(BufferedReader brdr) {
        LineRecord rec = null;
        boolean done = false;
        while (!done) {
            try {
                String inline = brdr.readLine();
                setLineCount(getLineCount() + 1);
                if (inline == null) {
                    done = true;
                } else {
                    String trimmedline = inline.trim();
                    if (!trimmedline.startsWith("#") && (trimmedline.length() > 0)) {
                        if (trimmedline.startsWith("@")) { // pragma line
                            if (trimmedline.startsWith("@classprefix")) {
                                setClassPrefix(
                                        trimmedline.substring(12,
                                        trimmedline.length()).trim());
                            }
                        } else {
                            if (rec == null) {
                                rec = new LineRecord(countIndent(inline),
                                        inline.trim());
                            } else {
                                rec.append(inline.trim());
                            }
                            if (!trimmedline.endsWith("\\")) {
                                done = true;
                            } else {
                                rec.trimEnd(1);
                            }
                        }
                    }
                }
            } catch (IOException ex) {
                printError("Unexpected end of input");
                done = true;
            }
        }
        return rec;
    }

    private static void printError(String string) {
        System.err.println(string + " at line " + getLineCount());
    }

    private static enum COLLTYPE {

        LIST, DICT, ORDEREDDICT, OBJECT, NOTACOLL
    };
    
    private static LineRecord recursiveParse(LineRecord rec,BufferedReader rdr,
            Object coll)
            throws IOException {

        String objectClass=null;
        int blockindent = rec.getIndent();
        while ((rec!= null)&&(rec.getIndent()==blockindent)) {
            LineRecord nextLine = getLine(rdr);
            if ((nextLine == null)||(rec.getIndent()>=nextLine.getIndent())){
                //simple tuple
                new Tuple(rec.getLine()).addTo(coll);
            } else {  //a collection    
                if (rec.getLine().contains("=")) { // illegal in a collection
                    printError("Collection cannot also assign a value");
                } else if (rec.getLine().endsWith(":")) {
                    rec.trimEnd(1);
                    Map dict = new HashMap();
                    new Tuple(rec.getLine(),dict).addTo(coll);
                     nextLine=recursiveParse(nextLine,rdr, dict);
                } else if (rec.getLine().endsWith("+")) {
                    rec.trimEnd(1);
                    Map dict = new LinkedHashMap();
                    new Tuple(rec.getLine(),dict).addTo(coll);
                     nextLine=recursiveParse(nextLine,rdr, dict);
                } else if (rec.getLine().contains(":")){ // must be object
                    objectClass = getClassPrefix()+
                            rec.getLine().substring(
                                rec.getLine().indexOf(':')+1,
                                rec.getLine().length()).trim();
                    rec.trimEnd(rec.getLine().length()-
                            rec.getLine().indexOf(':'));
                    Object newobj;
                    try {
                        newobj = Class.forName(objectClass).newInstance();
                        new Tuple(rec.getLine(),newobj).addTo(coll);
                        nextLine=recursiveParse(nextLine,rdr, newobj);
                    } catch (InstantiationException ex) {
                        Logger.getLogger(SADDL.class.getName()).log(Level.SEVERE, null, ex);
                         printError("Ckass objectClass "+ex.getMessage());
                    } catch (IllegalAccessException ex) {
                        Logger.getLogger(SADDL.class.getName()).log(Level.SEVERE, null, ex);
                         printError("Ckass objectClass "+ex.getMessage());
                    } catch (ClassNotFoundException ex) {
                        Logger.getLogger(SADDL.class.getName()).log(Level.SEVERE, null, ex);
                        printError("Ckass objectClass "+ex.getMessage());                    
                    }
                   
                } else {
                    List newList = new ArrayList();
                    new Tuple(rec.getLine(),newList).addTo(coll);
                    nextLine=recursiveParse(nextLine,rdr, newList);
                }                
            }  
            rec = nextLine;
        } 
        return rec;
    }


    /*
    private static LineRecord recursiveParse(LineRecord rec, BufferedReader rdr,
            Object coll)
            throws IOException {

        int blockIndent = rec.getIndent();
        Tuple currentTuple = null;
        COLLTYPE collType = COLLTYPE.NOTACOLL;
        String objectClass=null;
        do {
            if (rec.getIndent() == blockIndent) {
                if (rec.getLine().contains("=")) { // a value tuple
                    collType = COLLTYPE.NOTACOLL;
                } else if (rec.getLine().endsWith(":")) {
                    rec.trimEnd(1);
                    collType = COLLTYPE.DICT;
                } else if (rec.getLine().endsWith("+")) {
                    rec.trimEnd(1);
                    collType = COLLTYPE.ORDEREDDICT;
                } else if (rec.getLine().contains(":")){ // must be object
                    collType = COLLTYPE.OBJECT;
                    objectClass = getClassPrefix()+
                            rec.getLine().substring(
                                rec.getLine().indexOf(':')+1,
                                rec.getLine().length()).trim();
                    rec.trimEnd(rec.getLine().length()-
                            rec.getLine().indexOf(':'));
                } else {
                    collType = COLLTYPE.LIST;
                }
                currentTuple = new Tuple(rec.getLine());
                currentTuple.addTo(coll);
                rec = getLine(rdr);
            } else { // sub block
                switch (collType) {
                    case LIST:
                        List newList = new ArrayList();
                        rec = recursiveParse(rec, rdr, newList);
                        currentTuple.setValue(newList);
                        break;
                    case DICT:
                        Map newDict = new HashMap();
                        rec = recursiveParse(rec, rdr, newDict);
                        currentTuple.setValue(newDict);
                        break;
                    case ORDEREDDICT:
                        newDict = new LinkedHashMap();
                        rec = recursiveParse(rec, rdr, newDict);
                        currentTuple.setValue(newDict);
                        break;
                    case OBJECT:
                        try {
                            Class objclass = Class.forName(objectClass);
                            Object obj = objclass.newInstance();
                            rec = recursiveParse(rec,rdr,obj);
                            currentTuple.setValue(obj);
                        } catch (Exception e){
                            logger.throwing(SADDL.class.getName(),
                                    "recursiveParse",e);
                            printError(e.getMessage());
                        }
                        break;
                    default:
                        printError("Node not a collection tuple");
                }
            }
        } while ((rec != null) && (rec.getIndent() >= blockIndent));
        return rec;
    }
     * */

    static private class LineRecord {

        int indent;
        String line;

        LineRecord(int indent, String line) {
            this.indent = indent;
            this.line = line;
        }

        public void append(String line) {
            this.line += line;
        }

        public String getLine() {
            return line;
        }

        public int getIndent() {
            return indent;
        }

        private void trimEnd(int i) {
            line = line.substring(0, line.length() - i);
        }
    }

    public static void main(String[] arg0) { // test main

        try {
            // test main
            System.out.println(SADDL.parse(new FileReader("menubar.sadl")));
        } catch (IOException ex) {
            Logger.getLogger(SADDL.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}
